Skip to content

fix(compiler): allow re-assigning inferred local object to another class - #95

Closed
Tinywan wants to merge 1 commit into
swoole:masterfrom
Tinywan:wip/inferred-object-reassignment-pre-ssa
Closed

fix(compiler): allow re-assigning inferred local object to another class#95
Tinywan wants to merge 1 commit into
swoole:masterfrom
Tinywan:wip/inferred-object-reassignment-pre-ssa

Conversation

@Tinywan

@Tinywan Tinywan commented Sep 7, 2026

Copy link
Copy Markdown

A local variable has no type declaration; its class is inferred from the first assignment. Re-assigning an unrelated concrete class in a mutually exclusive branch (e.g. ReflectionFunction in if and ReflectionMethod in else) is valid PHP, but was rejected with "Cannot re-assign typed object".

Route both re-assignment sites through reassignInferredObjectVar(), which widens an inferred local to a generic dynamic object. Parameters, native objects, and explicitly declared object types keep the strict check.

A local variable has no type declaration; its class is inferred from the
first assignment. Re-assigning an unrelated concrete class in a mutually
exclusive branch (e.g. ReflectionFunction in `if` and ReflectionMethod in
`else`) is valid PHP, but was rejected with "Cannot re-assign typed object".

Route both re-assignment sites through reassignInferredObjectVar(), which
widens an inferred local to a generic dynamic object. Parameters, native
objects, and explicitly declared object types keep the strict check.
@matyhtf matyhtf closed this Sep 8, 2026
@matyhtf

matyhtf commented Sep 8, 2026

Copy link
Copy Markdown
Member

Thanks for the analysis and the patch, @Tinywan. We don't accept this direction — to be direct: TypePHP does not allow re-assigning a variable to a different type, so this PR will be closed.

Rationale: TypePHP follows an AOT / static-compilation model. A variable's type is fixed from its first assignment (or explicit declaration). Assigning two unrelated concrete classes across mutually exclusive if/else branches means "one variable carrying different types", which is a type violation. Silently widening an inferred local into a generic dynamic object on those branches — as reassignInferredObjectVar() does here — invalidates every later static check that relies on that variable and bypasses the guarantees provided by stableObjects / declaredObjects. This kind of implicit widening/narrowing should not exist.

If you genuinely need a variable to hold different types across branches, declare it explicitly as any:

$a = 'strlen';
$ref = std::any();                    // dynamic object: any type/class can be assigned later
if (is_string($a)) {
    $ref = new ReflectionFunction($a);
} else {
    $ref = new ReflectionMethod($a, 'count');
}

Declaring the intent with std::any() keeps the compiler's checks sound. If reflection or another use case turns out to be more common, please open an issue with a real code path and we can discuss whether friendlier sugar is warranted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants